use core::convert::Infallible;
use mlibs::cmsis_rtos2::kernel::os_delay;
use dht11::Dht11;
use embedded_hal::digital::v2::{InputPin, OutputPin};
use embedded_hal::blocking::delay::{DelayMs, DelayUs};
use serde::Serialize;
use mlibs::periph::tim::TIM2;
use mlibs::periph::config_io;
use mlibs::periph::gpio::PA;
use mlibs::periph::rcc::RCC;

#[config_io(
    DATA_PA2_OUT_OD
)]
struct Dht11Pin;

impl OutputPin for Dht11Pin {

    type Error = Infallible;

    fn set_high(&mut self) -> Result<(), Self::Error> {
        self.data_set_high();
        Ok(())
    }

    fn set_low(&mut self) -> Result<(), Self::Error> {
        self.data_set_low();
        Ok(())
    }
}

impl InputPin for Dht11Pin {

    type Error = Infallible;

    fn is_high(&self) -> Result<bool, Self::Error> {
        Ok( self.data_read() )
    }

    fn is_low(&self) -> Result<bool, Self::Error> {
        Ok( !self.data_read() )
    }
}

struct Dht11Delay;

impl DelayMs<u16> for Dht11Delay {

    fn delay_ms(&mut self, ms: u16) {
        os_delay(ms as u32);
    }
}

impl DelayUs<u16> for Dht11Delay {

    fn delay_us(&mut self, us: u16) {
        TIM2.cnt.write(|w| { w.cnt().bits(0) });
        while us > TIM2.cnt.read().cnt().bits() {}
    }
}

pub fn init() {
    Dht11Pin.init();
    RCC.apb1enr.modify(|_, w| {
        w.tim2en().enabled()
    });
    TIM2.cr1.reset();
    TIM2.psc.write(|w| {
        w.psc().variant(72 - 1)
    });
    TIM2.arr.write(|w| {
        w.arr().variant(0xFFFF)
    });
    TIM2.cr1.write(|w| {
        w.cen().enabled()
    });
}

#[derive(Debug, Serialize)]
pub struct Value {
    temperature: i16,
    humidity: u16,
}

pub fn read() -> Result<Value, dht11::Error<Infallible>> {
    let mut dht11 = Dht11::new(Dht11Pin);
    dht11.perform_measurement(&mut Dht11Delay).and_then(|v| {
        Ok(Value { temperature: v.temperature / 10, humidity: v.humidity / 10 })
    })
}
